package util;

import io.netty.buffer.ByteBuf;
import io.netty.util.internal.MathUtil;
import io.netty.util.internal.StringUtil;

import java.nio.ByteBuffer;


/**
 * @author Panwen Chen
 * @date 2021/4/12 15:59
 */
public class ByteBufferUtil {
  private static final char[] BYTE2CHAR = new char[256];
  private static final char[] HEXDUMP_TABLE = new char[256 * 4];
  private static final String[] HEXPADDING = new String[16];
  private static final String[] HEXDUMP_ROWPREFIXES = new String[65536 >>> 4];
  private static final String[] BYTE2HEX = new String[256];
  private static final String[] BYTEPADDING = new String[16];

  static {
    final char[] DIGITS = "0123456789abcdef".toCharArray();
    for (int i = 0; i < 256; i++) {
      HEXDUMP_TABLE[i << 1] = DIGITS[i >>> 4 & 0x0F];
      HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F];
    }

    int i;

    // Generate the lookup table for hex dump paddings
    for (i = 0; i < HEXPADDING.length; i++) {
      int padding = HEXPADDING.length - i;
      StringBuilder buf = new StringBuilder(padding * 3);
      for (int j = 0; j < padding; j++) {
        buf.append("   ");
      }
      HEXPADDING[i] = buf.toString();
    }

    // Generate the lookup table for the start-offset header in each row (up to 64KiB).
    for (i = 0; i < HEXDUMP_ROWPREFIXES.length; i++) {
      StringBuilder buf = new StringBuilder(12);
      buf.append(StringUtil.NEWLINE);
      buf.append(Long.toHexString(i << 4 & 0xFFFFFFFFL | 0x100000000L));
      buf.setCharAt(buf.length() - 9, '|');
      buf.append('|');
      HEXDUMP_ROWPREFIXES[i] = buf.toString();
    }

    // Generate the lookup table for byte-to-hex-dump conversion
    for (i = 0; i < BYTE2HEX.length; i++) {
      BYTE2HEX[i] = ' ' + StringUtil.byteToHexStringPadded(i);
    }

    // Generate the lookup table for byte dump paddings
    for (i = 0; i < BYTEPADDING.length; i++) {
      int padding = BYTEPADDING.length - i;
      StringBuilder buf = new StringBuilder(padding);
      for (int j = 0; j < padding; j++) {
        buf.append(' ');
      }
      BYTEPADDING[i] = buf.toString();
    }

    // Generate the lookup table for byte-to-char conversion
    for (i = 0; i < BYTE2CHAR.length; i++) {
      if (i <= 0x1f || i >= 0x7f) {
        BYTE2CHAR[i] = '.';
      } else {
        BYTE2CHAR[i] = (char) i;
      }
    }
  }

  /**
   * 打印所有内容
   *
   * @param buffer
   */
  public static void debugAll(ByteBuffer buffer) {
    int oldlimit = buffer.limit();
    buffer.limit(buffer.capacity());
    StringBuilder origin = new StringBuilder(256);
    appendPrettyHexDump(origin, buffer, 0, buffer.capacity());
    System.out.println("+--------+-------------------- all ------------------------+----------------+");
    System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), oldlimit);
    System.out.println(origin);
    buffer.limit(oldlimit);
  }

  /**
   * 打印可读取内容
   *
   * @param buffer
   */
  public static void debugRead(ByteBuffer buffer) {
    StringBuilder builder = new StringBuilder(256);
    appendPrettyHexDump(builder, buffer, buffer.position(), buffer.limit() - buffer.position());
    System.out.println("+--------+-------------------- read -----------------------+----------------+");
    System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), buffer.limit());
    System.out.println(builder);
  }

  private static void appendPrettyHexDump(StringBuilder dump, ByteBuffer buf, int offset, int length) {
    if (MathUtil.isOutOfBounds(offset, length, buf.capacity())) {
      throw new IndexOutOfBoundsException(
              "expected: " + "0 <= offset(" + offset + ") <= offset + length(" + length
                      + ") <= " + "buf.capacity(" + buf.capacity() + ')');
    }
    if (length == 0) {
      return;
    }
    dump.append(
            "         +-------------------------------------------------+" +
                    StringUtil.NEWLINE + "         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |" +
                    StringUtil.NEWLINE + "+--------+-------------------------------------------------+----------------+");

    final int startIndex = offset;
    final int fullRows = length >>> 4;
    final int remainder = length & 0xF;

    // Dump the rows which have 16 bytes.
    for (int row = 0; row < fullRows; row++) {
      int rowStartIndex = (row << 4) + startIndex;

      // Per-row prefix.
      appendHexDumpRowPrefix(dump, row, rowStartIndex);

      // Hex dump
      int rowEndIndex = rowStartIndex + 16;
      for (int j = rowStartIndex; j < rowEndIndex; j++) {
        dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);
      }
      dump.append(" |");

      // ASCII dump
      for (int j = rowStartIndex; j < rowEndIndex; j++) {
        dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);
      }
      dump.append('|');
    }

    // Dump the last row which has less than 16 bytes.
    if (remainder != 0) {
      int rowStartIndex = (fullRows << 4) + startIndex;
      appendHexDumpRowPrefix(dump, fullRows, rowStartIndex);

      // Hex dump
      int rowEndIndex = rowStartIndex + remainder;
      for (int j = rowStartIndex; j < rowEndIndex; j++) {
        dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);
      }
      dump.append(HEXPADDING[remainder]);
      dump.append(" |");

      // Ascii dump
      for (int j = rowStartIndex; j < rowEndIndex; j++) {
        dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);
      }
      dump.append(BYTEPADDING[remainder]);
      dump.append('|');
    }

    dump.append(StringUtil.NEWLINE +
            "+--------+-------------------------------------------------+----------------+");
  }

  private static void appendHexDumpRowPrefix(StringBuilder dump, int row, int rowStartIndex) {
    if (row < HEXDUMP_ROWPREFIXES.length) {
      dump.append(HEXDUMP_ROWPREFIXES[row]);
    } else {
      dump.append(StringUtil.NEWLINE);
      dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L));
      dump.setCharAt(dump.length() - 9, '|');
      dump.append('|');
    }
  }

  public static void log(ByteBuf buffer) {
    int length = buffer.readableBytes();
    int row = length / 16 + (length % 5 == 0 ? 0 : 1);
    StringBuilder buf = new StringBuilder(row * 80 * 2)
            .append("read index:").append(buffer.readerIndex())
            .append(" write index:").append(buffer.writerIndex())
            .append(" capacity:").append(buffer.capacity())
            .append(StringUtil.NEWLINE);
    io.netty.buffer.ByteBufUtil.appendPrettyHexDump(buf, buffer);
    System.out.println(buf.toString());
  }

  public static byte[] fill10bytes(char c, int len) {
    if (len > 10) {
      return null;
    }
    StringBuilder sb = new StringBuilder(c);
    for (int i = 0; i < len; i++) {
      sb.append(c);
    }

    for (int i = 0; i < 10 - len; i++) {
      sb.append("_");
    }
    return sb.toString().getBytes();
  }

  public static short getUnsignedByte(ByteBuffer buffer, int index) {
    return (short) (buffer.get(index) & 0xFF);
  }

  public static StringBuilder makeString(char c,int len){
    StringBuilder sb = new StringBuilder(len + 2);
    for (int i = 0; i < len; i++) {
      sb.append(c);
    }
    sb.append('\n');
    return sb;
  }
}
